/**
 * 
 */
package ufrj.safcp.tools.qrcode;

import java.awt.Image;
import java.awt.image.BufferedImage;
import java.nio.charset.Charset;
import java.util.Hashtable;
import java.util.Vector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.EncodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.GlobalHistogramBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

/**
 * 
 * Implementação da interface de manipulação de QRCode que utiliza a biblioteca
 * ZXing, disponível em http://code.google.com/p/zxing/ .
 * 
 * @author Bruno
 * 
 */
public class ZXingQRCodeHandler implements IQRCodeHandler {
	
	private static final Charset ISO_88591 = Charset.forName("ISO-8859-1");

	private static Logger logger = LoggerFactory.getLogger(ZXingQRCodeHandler.class);

	private int pixels;
	
	public static IQRCodeHandler getInstance() {
		return getInstance(100, true);
	}
	
	public static IQRCodeHandler getInstance(boolean compressDecoration) {
		return getInstance(100, compressDecoration);
	}
	
	public static IQRCodeHandler getInstance(int size) {
		return getInstance(size, true);
	}
	
	public static IQRCodeHandler getInstance(int size, boolean compressDecoration) {
		ZXingQRCodeHandler instance = new ZXingQRCodeHandler(size);
		if (compressDecoration) {
			return new ZlibCompressorQRCodeDataDecorator(instance);
		}
		return instance;
	}

	protected ZXingQRCodeHandler(int imagePixelsSize) {
		this.pixels = imagePixelsSize;
	}

	public BufferedImage encode(byte[] contents) {
		
		if (logger.isDebugEnabled()) {
			StringBuffer sb = new StringBuffer();
			sb.append("{");
			boolean first = true;
			for (byte b : contents) {
				if (first) {
					first = false;
				} else {
					sb.append(", ");
				}
				sb.append(b);
			}
			sb.append("}");
			logger.debug("Encoding byte[] " + sb);
		}
		QRCodeWriter codeWriter = new QRCodeWriter();
		
		int w = pixels;
		int h = pixels;
		
		BitMatrix bitMatrix;
		try {
			Hashtable<Object, Object> hints = new Hashtable<Object, Object>();
			hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
			bitMatrix = codeWriter.encode(new String(contents, ISO_88591), BarcodeFormat.QR_CODE, w, h, hints );
		} catch (WriterException e) {
			logger.error("Não foi possível codificar o conteúdo.");
			throw new RuntimeException(e);
		}
		
		if (logger.isDebugEnabled()) {
			logger.debug("w = " + w + ", cw = " + bitMatrix.getWidth());
			logger.debug("h = " + h + ", ch = " + bitMatrix.getHeight());
		}
		
		int i = 0;
		int [] imageData = new int[w * h];
		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {
				
				int r;
				int g;
				int b;
				
				if (bitMatrix.get(x,y)) {
					r = 0;
					g = 0;
					b = 0;
				} else {
					r = 255;
					g = 255;
					b = 255;
				}
				
				imageData[i++] = (255 << 24) | (r << 16) | (g << 8) | b;
			}
		}

		BufferedImage finalImage;
		finalImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);//TYPE_INT_ARGB
		finalImage.setRGB(0, 0, w, h, imageData, 0 ,w);
		return finalImage;
	}
	
	@SuppressWarnings("unchecked")
	public byte[] decode(Image contents) throws UnredableCodeException {
		if (contents == null) {
			throw new IllegalStateException("Could not decode image");
		}
		BufferedImage buffered = contents instanceof BufferedImage ? (BufferedImage)contents : new BufferedImageBuilder().bufferImage(contents);
		
		if (logger.isDebugEnabled()) {
			int width = buffered.getWidth();
			int height = buffered.getHeight();
			logger.debug("Decoding QRCode Image with " + width + "x" + height);
		}

		// Old API way
		//MonochromeBitmapSource source = new BufferedImageMonochromeBitmapSource(buffered);
		LuminanceSource ls = new BufferedImageLuminanceSource(buffered);
		//Binarizer binarizer = new HybridBinarizer(ls);
		Binarizer binarizer = new GlobalHistogramBinarizer(ls);
		BinaryBitmap image = new BinaryBitmap( binarizer);
		
		Result result;
		try {
			result = new QRCodeReader().decode(image);
			
			//Old API way
			//result = new MultiFormatReader().decode(source);
		} catch (ReaderException e) {
			logger.error("Não foi possível decodificar o conteúdo.");
			throw new UnredableCodeException(e);
		}
		
		/*
		 * Old API way
		Vector byteSegments = result.getByteSegments();
		 */
		
		Vector byteSegments = (Vector) result.getResultMetadata().get(ResultMetadataType.BYTE_SEGMENTS);  
		
		int i = 0;
		int tam = 0;
		for (Object o : byteSegments) {
			byte[] bs = (byte[])o;
			tam += bs.length;
		}
		
		byte[] resultBytes = new byte[tam];
		
		i = 0;
		for (Object o : byteSegments) {
			byte[] bs = (byte[])o;
			for (byte b : bs) {
				resultBytes[i++] = b;
			}
		}
		
		return resultBytes;
		
	}


}
